home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2010 April / PCWorld0410.iso / hity wydania / Ubuntu 9.10 PL / karmelkowy-koliberek-desktop-9.10-i386-PL.iso / casper / filesystem.squashfs / usr / share / pyshared / twisted / plugin.py < prev   
Text File  |  2008-07-29  |  8KB  |  247 lines

  1. # -*- test-case-name: twisted.test.test_plugin -*-
  2. # Copyright (c) 2005 Divmod, Inc.
  3. # Copyright (c) 2007 Twisted Matrix Laboratories.
  4. # See LICENSE for details.
  5.  
  6. """
  7. Plugin system for Twisted.
  8.  
  9. @author: Jp Calderone
  10. @author: Glyph Lefkowitz
  11. """
  12.  
  13. import os
  14. import sys
  15.  
  16. from zope.interface import Interface, providedBy
  17.  
  18. def _determinePickleModule():
  19.     """
  20.     Determine which 'pickle' API module to use.
  21.     """
  22.     try:
  23.         import cPickle
  24.         return cPickle
  25.     except ImportError:
  26.         import pickle
  27.         return pickle
  28.  
  29. pickle = _determinePickleModule()
  30.  
  31. from twisted.python.components import getAdapterFactory
  32. from twisted.python.reflect import namedAny
  33. from twisted.python import log
  34. from twisted.python.modules import getModule
  35.  
  36.  
  37.  
  38. class IPlugin(Interface):
  39.     """
  40.     Interface that must be implemented by all plugins.
  41.  
  42.     Only objects which implement this interface will be considered for return
  43.     by C{getPlugins}.  To be useful, plugins should also implement some other
  44.     application-specific interface.
  45.     """
  46.  
  47.  
  48.  
  49. class CachedPlugin(object):
  50.     def __init__(self, dropin, name, description, provided):
  51.         self.dropin = dropin
  52.         self.name = name
  53.         self.description = description
  54.         self.provided = provided
  55.         self.dropin.plugins.append(self)
  56.  
  57.     def __repr__(self):
  58.         return '<CachedPlugin %r/%r (provides %r)>' % (
  59.             self.name, self.dropin.moduleName,
  60.             ', '.join([i.__name__ for i in self.provided]))
  61.  
  62.     def load(self):
  63.         return namedAny(self.dropin.moduleName + '.' + self.name)
  64.  
  65.     def __conform__(self, interface, registry=None, default=None):
  66.         for providedInterface in self.provided:
  67.             if providedInterface.isOrExtends(interface):
  68.                 return self.load()
  69.             if getAdapterFactory(providedInterface, interface, None) is not None:
  70.                 return interface(self.load(), default)
  71.         return default
  72.  
  73.     # backwards compat HOORJ
  74.     getComponent = __conform__
  75.  
  76.  
  77.  
  78. class CachedDropin(object):
  79.     """
  80.     A collection of L{CachedPlugin} instances from a particular module in a
  81.     plugin package.
  82.  
  83.     @type moduleName: C{str}
  84.     @ivar moduleName: The fully qualified name of the plugin module this
  85.         represents.
  86.  
  87.     @type description: C{str} or C{NoneType}
  88.     @ivar description: A brief explanation of this collection of plugins
  89.         (probably the plugin module's docstring).
  90.  
  91.     @type plugins: C{list}
  92.     @ivar plugins: The L{CachedPlugin} instances which were loaded from this
  93.         dropin.
  94.     """
  95.     def __init__(self, moduleName, description):
  96.         self.moduleName = moduleName
  97.         self.description = description
  98.         self.plugins = []
  99.  
  100.  
  101.  
  102. def _generateCacheEntry(provider):
  103.     dropin = CachedDropin(provider.__name__,
  104.                           provider.__doc__)
  105.     for k, v in provider.__dict__.iteritems():
  106.         plugin = IPlugin(v, None)
  107.         if plugin is not None:
  108.             cachedPlugin = CachedPlugin(dropin, k, v.__doc__, list(providedBy(plugin)))
  109.     return dropin
  110.  
  111. try:
  112.     fromkeys = dict.fromkeys
  113. except AttributeError:
  114.     def fromkeys(keys, value=None):
  115.         d = {}
  116.         for k in keys:
  117.             d[k] = value
  118.         return d
  119.  
  120. def getCache(module):
  121.     """
  122.     Compute all the possible loadable plugins, while loading as few as
  123.     possible and hitting the filesystem as little as possible.
  124.  
  125.     @param module: a Python module object.  This represents a package to search
  126.     for plugins.
  127.  
  128.     @return: a dictionary mapping module names to CachedDropin instances.
  129.     """
  130.     allCachesCombined = {}
  131.     mod = getModule(module.__name__)
  132.     # don't want to walk deep, only immediate children.
  133.     lastPath = None
  134.     buckets = {}
  135.     # Fill buckets with modules by related entry on the given package's
  136.     # __path__.  There's an abstraction inversion going on here, because this
  137.     # information is already represented internally in twisted.python.modules,
  138.     # but it's simple enough that I'm willing to live with it.  If anyone else
  139.     # wants to fix up this iteration so that it's one path segment at a time,
  140.     # be my guest.  --glyph
  141.     for plugmod in mod.iterModules():
  142.         fpp = plugmod.filePath.parent()
  143.         if fpp not in buckets:
  144.             buckets[fpp] = []
  145.         bucket = buckets[fpp]
  146.         bucket.append(plugmod)
  147.     for pseudoPackagePath, bucket in buckets.iteritems():
  148.         dropinPath = pseudoPackagePath.child('dropin.cache')
  149.         try:
  150.             lastCached = dropinPath.getModificationTime()
  151.             dropinDotCache = pickle.load(dropinPath.open('rb'))
  152.         except:
  153.             dropinDotCache = {}
  154.             lastCached = 0
  155.  
  156.         needsWrite = False
  157.         existingKeys = {}
  158.         for pluginModule in bucket:
  159.             pluginKey = pluginModule.name.split('.')[-1]
  160.             existingKeys[pluginKey] = True
  161.             if ((pluginKey not in dropinDotCache) or
  162.                 (pluginModule.filePath.getModificationTime() >= lastCached)):
  163.                 needsWrite = True
  164.                 try:
  165.                     provider = pluginModule.load()
  166.                 except:
  167.                     # dropinDotCache.pop(pluginKey, None)
  168.                     log.err()
  169.                 else:
  170.                     entry = _generateCacheEntry(provider)
  171.                     dropinDotCache[pluginKey] = entry
  172.         # Make sure that the cache doesn't contain any stale plugins.
  173.         for pluginKey in dropinDotCache.keys():
  174.             if pluginKey not in existingKeys:
  175.                 del dropinDotCache[pluginKey]
  176.                 needsWrite = True
  177.         if needsWrite:
  178.             try:
  179.                 dropinPath.setContent(pickle.dumps(dropinDotCache))
  180.             except:
  181.                 log.err()
  182.         allCachesCombined.update(dropinDotCache)
  183.     return allCachesCombined
  184.  
  185.  
  186. def getPlugins(interface, package=None):
  187.     """
  188.     Retrieve all plugins implementing the given interface beneath the given module.
  189.  
  190.     @param interface: An interface class.  Only plugins which implement this
  191.     interface will be returned.
  192.  
  193.     @param package: A package beneath which plugins are installed.  For
  194.     most uses, the default value is correct.
  195.  
  196.     @return: An iterator of plugins.
  197.     """
  198.     if package is None:
  199.         import twisted.plugins as package
  200.     allDropins = getCache(package)
  201.     for dropin in allDropins.itervalues():
  202.         for plugin in dropin.plugins:
  203.             try:
  204.                 adapted = interface(plugin, None)
  205.             except:
  206.                 log.err()
  207.             else:
  208.                 if adapted is not None:
  209.                     yield adapted
  210.  
  211.  
  212. # Old, backwards compatible name.  Don't use this.
  213. getPlugIns = getPlugins
  214.  
  215.  
  216. def pluginPackagePaths(name):
  217.     """
  218.     Return a list of additional directories which should be searched for
  219.     modules to be included as part of the named plugin package.
  220.  
  221.     @type name: C{str}
  222.     @param name: The fully-qualified Python name of a plugin package, eg
  223.         C{'twisted.plugins'}.
  224.  
  225.     @rtype: C{list} of C{str}
  226.     @return: The absolute paths to other directories which may contain plugin
  227.         modules for the named plugin package.
  228.     """
  229.     package = name.split('.')
  230.     # Note that this may include directories which do not exist.  It may be
  231.     # preferable to remove such directories at this point, rather than allow
  232.     # them to be searched later on.
  233.     #
  234.     # Note as well that only '__init__.py' will be considered to make a
  235.     # directory a package (and thus exclude it from this list).  This means
  236.     # that if you create a master plugin package which has some other kind of
  237.     # __init__ (eg, __init__.pyc) it will be incorrectly treated as a
  238.     # supplementary plugin directory.
  239.     return [
  240.         os.path.abspath(os.path.join(x, *package))
  241.         for x
  242.         in sys.path
  243.         if
  244.         not os.path.exists(os.path.join(x, *package + ['__init__.py']))]
  245.  
  246. __all__ = ['getPlugins', 'pluginPackagePaths']
  247.